#include "instructions/references/put_field.hpp"
#include "rtda/frame.h"
#include "rtda/heap/cp_fieldref.h"
#include <stdexcept>

namespace jvm {
namespace instructions {

void PutField::Execute(Frame& frame) {
    auto currentMethod = frame.GetMethod();
    auto currentClass = currentMethod->GetClass();
    auto cp = currentClass->GetConstantPool();
    auto fieldRef = std::dynamic_pointer_cast<FieldRef>(cp->GetConstant(index_));
    auto field = fieldRef->ResolvedField();

    if (field->IsStatic()) {
        throw std::runtime_error("java.lang.IncompatibleClassChangeError");
    }
    if (field->IsFinal()) {
        if (currentClass != field->GetClass() || currentMethod->GetName() != "<init>") {
            throw std::runtime_error("java.lang.IllegalAccessError");
        }
    }

    auto descriptor = field->GetDescriptor();
    auto slotId = field->GetSlotId();
    auto stack = frame.GetOperandStack();

    switch (descriptor[0]) {
        case 'Z': case 'B': case 'C': case 'S': case 'I': {
            auto val = stack->PopInt();
            auto ref = stack->PopRef();
            if (!ref) {
                throw std::runtime_error("java.lang.NullPointerException");
            }
            ref->GetFields()->SetInt(slotId, val);
            break;
        }
        case 'F': {
            auto val = stack->PopFloat();
            auto ref = stack->PopRef();
            if (!ref) {
                throw std::runtime_error("java.lang.NullPointerException");
            }
            ref->GetFields()->SetFloat(slotId, val);
            break;
        }
        case 'J': {
            auto val = stack->PopLong();
            auto ref = stack->PopRef();
            if (!ref) {
                throw std::runtime_error("java.lang.NullPointerException");
            }
            ref->GetFields()->SetLong(slotId, val);
            break;
        }
        case 'D': {
            auto val = stack->PopDouble();
            auto ref = stack->PopRef();
            if (!ref) {
                throw std::runtime_error("java.lang.NullPointerException");
            }
            ref->GetFields()->SetDouble(slotId, val);
            break;
        }
        case 'L': case '[': {
            auto val = stack->PopRef();
            auto ref = stack->PopRef();
            if (!ref) {
                throw std::runtime_error("java.lang.NullPointerException");
            }
            ref->GetFields()->SetRef(slotId, val);
            break;
        }
        default:
            // TODO: Handle other cases
            break;
    }
}

} // namespace instructions
} // namespace jvm